In this R Notebook we preprocess spatial and corresponding reference scRNA-seq data of human Idiopathic pulmonary fibrosis (IPF) lung for cell type deconvolution.

  1. Spatial data preprocessing:

    1.1 Input original data files

    Here we provide a R rds file IPF_spatial_data.rds containing the Seurat object of the IPF lung sample we are working on. The Seurat object contains raw nUMI and physical locations of spatial spots, and a thumbnail of tissue image. In total 4,992 spatial spots and 60,651 genes are included in the raw data.

    The raw data and tissue image have also been uploaded to GSE231385.

    1.2 Output data files for cell type deconvolution

    We filter out the spatial spots NOT covered by the tissue and the genes NOT expressed in any tissue coverd spots, remaining 3,532 spots and 32,078 genes.

  2. Reference scRNA-seq data preprocessing:

    2.1 Input original data files

    raw nUMI count matrix and cell type annotation of scRNA-seq data can be downloaded from GSE136831, including 312,928 cells and 45,947 genes.

    Here we only use cells from one IPF subject 225I, and provide a R rds file IPF_scRNA_data.rds containing a Seurat object with 12,070 cells and 60,651 genes. To process the scRNA-seq data of this subject, we use STARsolo to map the reads to reference genome (GRCh38), and refined the cell type annotation. So both the nUMI count matrix and cell type annotation are different with the published version in GSE136831.

    Note that we set all entries of the sparse matrix in data slot as 0 to decrease the file size.

    2.2 Output data files for cell type deconvolution

    We select 26 major cell types to work on, and filter out cells of other cell types and genes NOT expressed in any selected cells, remaining 11,227 cells and 35,483 genes.

    • Raw nUMI of 11,227 cells with 26 cell types and 35,483 genes: IPF_ref_scRNA_cell_nUMI.csv.gz.

    • Cell type annotation for those 11,227 cells: IPF_ref_scRNA_cell_celltype.csv.

    • We also manually select 2,534 cell type specific marker genes from the filtered data. To use it in SDePER cell type deconvolution, we create a 26 * 2,534 matrix with all entries are 0, and set the selected 26 cell types as row names and 2,534 marker genes as column names. This cell type maker gene expression profile is saved in IPF_selected_2534_celltype_markers.csv.

1 Version

version[['version.string']]
[1] "R version 4.2.2 Patched (2022-11-10 r83330)"
print(sprintf('Seurat package version: %s', packageVersion('Seurat')))
[1] "Seurat package version: 4.1.1"

2 Preprocess IPF spatial dataset

2.1 Read original data file IPF_spatial_data.rds

file_name = file.path(home.dir, 'IPF_spatial_data.rds')
org_data = readRDS(file_name)
print(sprintf('load data from %s', file_name))
[1] "load data from /home/hill103/Documents/SharedFolder/ToHost/CVAE-GLRM_Analysis/RealData/IPF/IPF_spatial_data.rds"
print(sprintf('spots: %d; genes: %d', ncol(org_data), nrow(org_data)))
[1] "spots: 4992; genes: 60651"

2.2 Extract spots covered by tissue

# add tissue indicator into meta data
total_spots = ncol(org_data)
tmp.df = org_data@images[[1]]@coordinates
tmp.df = tmp.df[colnames(org_data), ]
stopifnot(nrow(tmp.df) == ncol(org_data))
org_data[['tissue']] = tmp.df$tissue

# subset spots covered by tissue
org_data = subset(org_data, subset = tissue==1)
print(sprintf('spots covered by tissue: %d (%.2f%%)', ncol(org_data), ncol(org_data)/total_spots*100))
[1] "spots covered by tissue: 3532 (70.75%)"

Tissue images highlight spots covered by tissue

Idents(org_data) = 'tissue'
SpatialDimPlot(org_data, crop = F, alpha=0.2, cols=c('1'='blue', '0'='red')) + NoLegend()

2.3 Filter out genes not detected in any spots within this sample

Keep genes detected in >=1 spot

tmp = Matrix::rowSums(org_data@assays$spatial@counts)
need.genes = names(tmp)[tmp>0]
print(sprintf('finally keep %d genes', length(need.genes)))
[1] "finally keep 32078 genes"
  
# subset genes
org_data = subset(org_data, features=need.genes)

Violin plot of genes expressed in spots

tmp = data.frame(spot_sum=Matrix::colSums(org_data@assays$spatial@counts>0))
ggplot(tmp, aes(x='', y=spot_sum)) +
  geom_violin(fill='orange', width=1) +
  geom_jitter(size=0.1, position=position_jitter(0.1)) +
  scale_y_continuous(limits=c(0, 9500), breaks=seq(0, 9500, 500)) +
  labs(x='', y='#genes expressed in spot')

2.4 Save files for deconvolution

2.4.1 Spatial spot nUMI

Save spots and genes after filtering into file IPF_spatial_spot_nUMI.csv.gz. Rows as spatial spots and columns as genes.

data.table::fwrite(as.data.frame(as.matrix(Matrix::t(org_data@assays$spatial@counts))), 'IPF_spatial_spot_nUMI.csv.gz', row.names = T)
print(sprintf('save %d gene nUMIs of %d spatial spots into file %s', ncol(org_data), nrow(org_data), 'IPF_spatial_spot_nUMI.csv.gz'))
[1] "save 3532 gene nUMIs of 32078 spatial spots into file IPF_spatial_spot_nUMI.csv.gz"

2.4.2 Physical Locations of spatial spots

The x and y coordinates of spatial spots are from col and row, respectively. col and row are generated by 10x Space Ranger. Save it into file IPF_spatial_spot_loc.csv.

local_df = org_data@images$x1@coordinates %>%
  select(c('col', 'row'))

colnames(local_df) = c('x', 'y')

local_df[1:5, ]

write.csv(local_df, 'IPF_spatial_spot_loc.csv')
print(sprintf('save Physical Locations of spatial spots into file %s', 'IPF_spatial_spot_loc.csv'))
[1] "save Physical Locations of spatial spots into file IPF_spatial_spot_loc.csv"

2.4.3 Adjacency Matrix of spatial spots

We define the neighborhood of a spatial spot contains the adjacent left, right, top and bottom spot, plus the second closest spots at left and right, that is, one spot has at most 6 neighbors.

The generated Adjacency Matrix A only contains 1 and 0, where 1 represents corresponding two spots are adjacent spots according to the definition of neighborhood, while value 0 for non-adjacent spots. Note all diagonal entries are 0s.

Adjacency Matrix are saved into file IPF_spatial_spot_adjacency_matrix.csv.

getNeighbour = function(array_row, array_col) {
  # based on the (row, col) of one spot, return the (row, col) of all 6 neighbours
  return(list(c(array_row-1, array_col-1),
              c(array_row-1, array_col+1),
              c(array_row, array_col-2),
              c(array_row, array_col+2),
              c(array_row+1, array_col-1),
              c(array_row+1, array_col+1)))
}

# adjacency matrix
A = matrix(0, nrow = nrow(local_df), ncol = nrow(local_df))
row.names(A) = rownames(local_df)
colnames(A) = rownames(local_df)
for (i in 1:nrow(local_df)) {
  barcode = rownames(local_df)[i]
  array_row = local_df[i, 'y']
  array_col = local_df[i, 'x']
  
  # get neighbors
  neighbours = getNeighbour(array_row, array_col)
  
  # fill the adjacency matrix
  for (this.vec in neighbours) {
    tmp.p = rownames(local_df[local_df$y==this.vec[1] & local_df$x==this.vec[2], ])
    
    if (length(tmp.p) >= 1) {
      # target spots have neighbors in selected spots
      for (neigh.barcode in tmp.p) {
        A[barcode, neigh.barcode] = 1
      }
    }
  }
}

A[1:5, 1:5]
                     x1__AAACAAGTATCTCCCA x1__AAACAATCTACTAGCA x1__AAACAGAGCGACTCCT x1__AAACAGTGTTCCTGGG x1__AAACATTTCCCGGATT
x1__AAACAAGTATCTCCCA                    0                    0                    0                    0                    0
x1__AAACAATCTACTAGCA                    0                    0                    0                    0                    0
x1__AAACAGAGCGACTCCT                    0                    0                    0                    0                    0
x1__AAACAGTGTTCCTGGG                    0                    0                    0                    0                    0
x1__AAACATTTCCCGGATT                    0                    0                    0                    0                    0
write.csv(A, 'IPF_spatial_spot_adjacency_matrix.csv')
print(sprintf('save Adjacency Matrix of spatial spots into file %s', 'IPF_spatial_spot_adjacency_matrix.csv'))
[1] "save Adjacency Matrix of spatial spots into file IPF_spatial_spot_adjacency_matrix.csv"

Plot Adjacency Matrix. Each node is spot, spots within neighborhood are connected with edges.

Note reverse y axis to to make the origin (0,0) at top left

g = graph_from_adjacency_matrix(A, 'undirected', add.colnames = NA, add.rownames = NA)
# manually set nodes x and y coordinates
vertex_attr(g, name = 'x') = local_df$x
vertex_attr(g, name = 'y') = local_df$y
# reverse y axis to make the (0,0) at top left
plot(g, vertex.size=2, edge.width=4, margin=-0.05, ylim=c(1, -1))

3 Preprocess reference scRNA-seq data

3.1 Read original data file IPF_scRNA_data.rds

file_name = file.path(home.dir, 'IPF_scRNA_data.rds')
ref_data = readRDS(file_name)
print(sprintf('load data from %s', file_name))
[1] "load data from /home/hill103/Documents/SharedFolder/ToHost/CVAE-GLRM_Analysis/RealData/IPF/IPF_scRNA_data.rds"
celltype_category_order = c("Myeloid", "Endothelial", "Stromal", "Lymphoid", "Epithelial", "Multiplet") 

print(sprintf('cells: %d; genes: %d', ncol(ref_data), nrow(ref_data)))
[1] "cells: 12070; genes: 60651"
print(sprintf('total %d distinct cell types', length(unique(ref_data$celltype))))
[1] "total 44 distinct cell types"

cell count by cell types

meta.data = ref_data@meta.data
meta.data = meta.data %>% group_by(celltype_category, celltype) %>% summarise(count=n())
`summarise()` has grouped output by 'celltype_category'. You can override using the `.groups` argument.
meta.data$celltype_category = factor(meta.data$celltype_category, levels = celltype_category_order)

ggplot(meta.data, aes(x=celltype, y=count, label=count)) +
  geom_bar(position=position_dodge2(width=0.9, preserve="single"), stat="identity") +
  geom_text(position=position_dodge2(width=0.9, preserve="single"), vjust=0.5, hjust=-0.1, angle=90) +
  theme_bw() +
  xlab("") +
  ylab("# of Cells") +
  theme(axis.text.x=element_text(angle=45, vjust=1, hjust=1),
        legend.position="bottom",
        strip.placement = "outside",
        strip.background = element_rect(fill=NA, colour="grey50"),
        panel.spacing=unit(0,"cm")) +
  facet_grid(~ celltype_category, space="free_x", scales="free_x", switch="x")

3.2 Filter on cells

We only focus on 26 major cell types

  • “Myeloid”: “Macrophage”, “Macrophage_Alveolar”, “cDC1”, “cDC2”, “cMonocyte”, “ncMonocyte”, “Mast”
  • “Endothelial”: “VE_Arterial”, “VE_Venous”, “VE_Capillary_A”, “VE_Capillary_B”, “Lymphatic”,
  • “Stromal”: “Fibroblast-Adventitial”, “Fibroblast-Airway”, “Fibroblast-Alveolar”, “Pericyte-Alveolar”, “SMC-Vascular”
  • “Lymphoid”: “T”, “B”, “NK”
  • “Epithelial”: “Ciliated”, “Basal”, “Goblet”, “ATI”, “ATII”, “AberrantBasaloid”
need_celltypes = c("Macrophage", "Macrophage_Alveolar", "cDC1", "cDC2", "cMonocyte", "ncMonocyte", "Mast",
                   "VE_Arterial", "VE_Venous", "VE_Capillary_A", "VE_Capillary_B", "Lymphatic",
                   "Fibroblast-Adventitial", "Fibroblast-Airway", "Fibroblast-Alveolar", "Pericyte-Alveolar", "SMC-Vascular",
                   "T", "B", "NK",
                   "Ciliated", "Basal", "Goblet", "ATI", "ATII", "AberrantBasaloid")
ref_data = subset(ref_data, subset = celltype %in% need_celltypes)
print(sprintf('remain cells: %d; genes: %d', ncol(ref_data), nrow(ref_data)))
[1] "remain cells: 11227; genes: 60651"

cell count of those 26 major cell types

meta.data = ref_data@meta.data
meta.data = meta.data %>% group_by(celltype_category, celltype) %>% summarise(count=n())
`summarise()` has grouped output by 'celltype_category'. You can override using the `.groups` argument.
meta.data$celltype_category = factor(meta.data$celltype_category, levels = celltype_category_order)
meta.data$celltype = factor(meta.data$celltype, levels = need_celltypes)

ggplot(meta.data, aes(x=celltype, y=count, label=count)) +
  geom_bar(position=position_dodge2(width=0.9, preserve="single"), stat="identity") +
  geom_text(position=position_dodge2(width=0.9, preserve="single"), vjust=0.5, hjust=-0.1, angle=90) +
  theme_bw() +
  xlab("") +
  ylab("# of Cells") +
  theme(axis.text.x=element_text(angle=45, vjust=1, hjust=1),
        legend.position="bottom",
        strip.placement = "outside",
        strip.background = element_rect(fill=NA, colour="grey50"),
        panel.spacing=unit(0,"cm")) +
  facet_grid(~ celltype_category, space="free_x", scales="free_x", switch="x") +
  ylim(0, 5400)

3.3 Filter on genes

We excludes genes NOT expressed in any cells within the 26 major cell types

gene_sum = Matrix::rowSums(ref_data@assays$RNA@counts)
keep_genes = names(gene_sum)[gene_sum>0]
ref_data = subset(ref_data, features = keep_genes)
print(sprintf('remain cells: %d; genes: %d', ncol(ref_data), nrow(ref_data)))
[1] "remain cells: 11227; genes: 35483"

3.4 Save files

Save cell type annotation of selected cells to file IPF_ref_scRNA_cell_celltype.csv.

write.csv(ref_data@meta.data[, 'celltype', drop=F], 'IPF_ref_scRNA_cell_celltype.csv')
print(sprintf('save cell type annotation of reference scRNA-seq cells into file %s', 'IPF_ref_scRNA_cell_celltype.csv'))
[1] "save cell type annotation of reference scRNA-seq cells into file IPF_ref_scRNA_cell_celltype.csv"

Save scRNA-seq nUMI matrix to file IPF_ref_scRNA_cell_nUMI.csv.gz

ref_df = as.data.frame(as.matrix(Matrix::t(ref_data@assays$RNA@counts)), check.names=F)
ref_df[1:5, 1:5]

data.table::fwrite(ref_df, 'IPF_ref_scRNA_cell_nUMI.csv.gz', row.names = T)

Written 21.6% of 11227 rows in 2 secs using 4 threads. maxBuffUsed=2%. ETA 7 secs.      
Written 31.3% of 11227 rows in 3 secs using 4 threads. maxBuffUsed=2%. ETA 6 secs.      
Written 41.1% of 11227 rows in 4 secs using 4 threads. maxBuffUsed=2%. ETA 5 secs.      
Written 50.4% of 11227 rows in 5 secs using 4 threads. maxBuffUsed=2%. ETA 4 secs.      
Written 56.9% of 11227 rows in 6 secs using 4 threads. maxBuffUsed=2%. ETA 4 secs.      
Written 63.5% of 11227 rows in 7 secs using 4 threads. maxBuffUsed=2%. ETA 4 secs.      
Written 69.6% of 11227 rows in 8 secs using 4 threads. maxBuffUsed=2%. ETA 3 secs.      
Written 75.8% of 11227 rows in 9 secs using 4 threads. maxBuffUsed=2%. ETA 2 secs.      
Written 82.2% of 11227 rows in 10 secs using 4 threads. maxBuffUsed=2%. ETA 2 secs.      
Written 89.1% of 11227 rows in 11 secs using 4 threads. maxBuffUsed=2%. ETA 1 secs.      
Written 96.2% of 11227 rows in 12 secs using 4 threads. maxBuffUsed=2%. ETA 0 secs.      
                                                                                                                                     
print(sprintf('save nUMI matrix of reference scRNA-seq cells into gzip compressed file %s', 'IPF_ref_scRNA_cell_nUMI.csv.gz'))
[1] "save nUMI matrix of reference scRNA-seq cells into gzip compressed file IPF_ref_scRNA_cell_nUMI.csv.gz"
LS0tCnRpdGxlOiAiUHJlcHJvY2VzcyBJUEYgZGF0YSBmb3IgY2VsbCB0eXBlIGRlY29udm9sdXRpb24iCmF1dGhvcjogIk5pbmdzaGFuIExpICYgSmlheWkgWmhhbyAmIFl1bnFpbmcgTGl1IgpkYXRlOiAiMjAyMy8wNC8yMSIKb3V0cHV0OiAKICBodG1sX25vdGVib29rOgogICAgY29kZV9mb2xkaW5nOiBoaWRlCiAgICBoaWdobGlnaHQ6IHRhbmdvCiAgICBudW1iZXJfc2VjdGlvbnM6IHllcwogICAgdGhlbWU6IHVuaXRlZAogICAgdG9jOiB5ZXMKICAgIHRvY19kZXB0aDogNgogICAgdG9jX2Zsb2F0OiB5ZXMKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCBldmFsID0gVFJVRSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UsIHJlc3VsdHM9J2hvbGQnLCBmaWcud2lkdGggPSA3LCBmaWcuaGVpZ2h0ID0gNSwgZHBpID0gMzAwKQoKCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShpZ3JhcGgpCmxpYnJhcnkoU2V1cmF0KQoKYCVub3RpbiVgID0gTmVnYXRlKGAlaW4lYCkKCnNldC5zZWVkKDEpCgpob21lLmRpciA9ICcvaG9tZS9oaWxsMTAzL0RvY3VtZW50cy9TaGFyZWRGb2xkZXIvVG9Ib3N0L0NWQUUtR0xSTV9BbmFseXNpcy9SZWFsRGF0YS9JUEYnCgoKbXkuZGlzdGluY3QuY29sb3JzMjAgPSBjKCIjZTYxOTRiIiwgIiMzY2I0NGIiLCAiI2ZmZTExOSIsICIjNDM2M2Q4IiwgIiNmNTgyMzEiLCAiIzkxMWViNCIsICIjNDZmMGYwIiwgIiNmMDMyZTYiLCAiI2JjZjYwYyIsICIjZmFiZWJlIiwgIiMwMDgwODAiLCAiIzlhNjMyNCIsICIjODAwMDAwIiwgIiNhYWZmYzMiLCAiIzgwODAwMCIsICIjMDAwMDc1IiwgIiM4MDgwODAiLCAiI2U2YmVmZiIsICIjZmZkOGIxIiwgIiMwMDAwMDAiKQoKbXkuZGlzdGluY3QuY29sb3JzNDAgPSBjKCIjMDBmZjAwIiwiI2ZmNDUwMCIsIiMwMGNlZDEiLCIjNTU2YjJmIiwiI2EwNTIyZCIsIiM4YjAwMDAiLCIjODA4MDAwIiwiIzQ4M2Q4YiIsIiMwMDgwMDAiLCIjMDA4MDgwIiwiIzQ2ODJiNCIsIiMwMDAwODAiLCIjOWFjZDMyIiwiI2RhYTUyMCIsIiM3ZjAwN2YiLCIjOGZiYzhmIiwiI2IwMzA2MCIsIiNkMmI0OGMiLCIjNjk2OTY5IiwiI2ZmOGMwMCIsIiMwMGZmN2YiLCIjZGMxNDNjIiwiI2Y0YTQ2MCIsIiMwMDAwZmYiLCIjYTAyMGYwIiwiI2FkZmYyZiIsIiNmZjAwZmYiLCIjMWU5MGZmIiwiI2YwZTY4YyIsIiNmYTgwNzIiLCIjZmZmZjU0IiwiI2RkYTBkZCIsIiM4N2NlZWIiLCIjN2I2OGVlIiwiI2VlODJlZSIsIiM5OGZiOTgiLCIjN2ZmZmQ0IiwiI2ZmYjZjMSIsIiNkY2RjZGMiLCIjMDAwMDAwIikKYGBgCgoKSW4gdGhpcyBSIE5vdGVib29rIHdlIHByZXByb2Nlc3Mgc3BhdGlhbCBhbmQgY29ycmVzcG9uZGluZyByZWZlcmVuY2Ugc2NSTkEtc2VxIGRhdGEgb2YgaHVtYW4gKipJZGlvcGF0aGljIHB1bG1vbmFyeSBmaWJyb3NpcyAoSVBGKSBsdW5nKiogZm9yIGNlbGwgdHlwZSBkZWNvbnZvbHV0aW9uLgoKMS4gKipTcGF0aWFsIGRhdGEgcHJlcHJvY2Vzc2luZyoqOgoKICAgIDEuMSBJbnB1dCBvcmlnaW5hbCBkYXRhIGZpbGVzCiAgICAKICAgIEhlcmUgd2UgcHJvdmlkZSBhIFIgcmRzIGZpbGUgW0lQRl9zcGF0aWFsX2RhdGEucmRzXShodHRwczovL2dpdGh1Yi5jb20vYXo3amgyL1NEZVBFUl9BbmFseXNpcy9ibG9iL21haW4vUmVhbERhdGEvSVBGL0lQRl9zcGF0aWFsX2RhdGEucmRzKSBjb250YWluaW5nIHRoZSAqKmBTZXVyYXRgIG9iamVjdCoqIG9mIHRoZSBJUEYgbHVuZyBzYW1wbGUgd2UgYXJlIHdvcmtpbmcgb24uIFRoZSBgU2V1cmF0YCBvYmplY3QgY29udGFpbnMgKipyYXcgblVNSSoqIGFuZCAqKnBoeXNpY2FsIGxvY2F0aW9ucyoqIG9mIHNwYXRpYWwgc3BvdHMsIGFuZCBhIHRodW1ibmFpbCBvZiAqKnRpc3N1ZSBpbWFnZSoqLiBJbiB0b3RhbCA0LDk5MiBzcGF0aWFsIHNwb3RzIGFuZCA2MCw2NTEgZ2VuZXMgYXJlIGluY2x1ZGVkIGluIHRoZSByYXcgZGF0YS4gCiAgICAKICAgIFRoZSByYXcgZGF0YSBhbmQgdGlzc3VlIGltYWdlIGhhdmUgYWxzbyBiZWVuIHVwbG9hZGVkIHRvIFtHU0UyMzEzODVdKGh0dHBzOi8vd3d3Lm5jYmkubmxtLm5paC5nb3YvZ2VvL3F1ZXJ5L2FjYy5jZ2k/YWNjPUdTRTIzMTM4NSkuCiAgICAKICAgIDEuMiBPdXRwdXQgZGF0YSBmaWxlcyBmb3IgY2VsbCB0eXBlIGRlY29udm9sdXRpb24KICAgIAogICAgV2UgZmlsdGVyIG91dCB0aGUgKipzcGF0aWFsIHNwb3RzIE5PVCBjb3ZlcmVkIGJ5IHRoZSB0aXNzdWUqKiBhbmQgdGhlICoqZ2VuZXMgTk9UIGV4cHJlc3NlZCBpbiBhbnkgdGlzc3VlIGNvdmVyZCBzcG90cyoqLCByZW1haW5pbmcgMyw1MzIgc3BvdHMgYW5kIDMyLDA3OCBnZW5lcy4KCiAgICAqIFJhdyBuVU1JIG9mIHNwYXRpYWwgc3BvdHM6IFtJUEZfc3BhdGlhbF9zcG90X25VTUkuY3N2XShodHRwczovL2dpdGh1Yi5jb20vYXo3amgyL1NEZVBFUl9BbmFseXNpcy9ibG9iL21haW4vUmVhbERhdGEvSVBGL0lQRl9zcGF0aWFsX3Nwb3RfblVNSS5jc3YpLgogICAgKiBQaHlzaWNhbCBsb2NhdGlvbiBvZiBzcGF0aWFsIHNwb3RzOiBbSVBGX3NwYXRpYWxfc3BvdF9sb2MuY3N2XShodHRwczovL2dpdGh1Yi5jb20vYXo3amgyL1NEZVBFUl9BbmFseXNpcy9ibG9iL21haW4vUmVhbERhdGEvSVBGL0lQRl9zcGF0aWFsX3Nwb3RfbG9jLmNzdikuIFRoZSBgeGAgYW5kIGB5YCBjb29yZGluYXRlcyBvZiBzcGF0aWFsIHNwb3RzIGFyZSBmcm9tIGBjb2xgIGFuZCBgcm93YCwgcmVzcGVjdGl2ZWx5LgogICAgKiBBZGphY2VuY3kgTWF0cml4OiBbSVBGX3NwYXRpYWxfc3BvdF9hZGphY2VuY3lfbWF0cml4LmNzdl0oaHR0cHM6Ly9naXRodWIuY29tL2F6N2poMi9TRGVQRVJfQW5hbHlzaXMvYmxvYi9tYWluL1JlYWxEYXRhL0lQRi9JUEZfc3BhdGlhbF9zcG90X2FkamFjZW5jeV9tYXRyaXguY3N2KS4gU3BvdHMgd2l0aGluIG5laWdoYm9yaG9vZCBhcmUgYWRqYWNlbnQgKipsZWZ0KiosICoqcmlnaHQqKiwgKip0b3AqKiBhbmQgKipib3R0b20qKiBzcG90cywgcGx1cyAqKnRoZSBzZWNvbmQgY2xvc2VzdCBzcG90cyBhdCBsZWZ0IGFuZCByaWdodCoqLgoKCjIuICoqUmVmZXJlbmNlIHNjUk5BLXNlcSBkYXRhIHByZXByb2Nlc3NpbmcqKjoKCiAgICAyLjEgSW5wdXQgb3JpZ2luYWwgZGF0YSBmaWxlcwogICAgCiAgICByYXcgblVNSSBjb3VudCBtYXRyaXggYW5kIGNlbGwgdHlwZSBhbm5vdGF0aW9uIG9mIHNjUk5BLXNlcSBkYXRhIGNhbiBiZSBkb3dubG9hZGVkIGZyb20gW0dTRTEzNjgzMV0oaHR0cHM6Ly93d3cubmNiaS5ubG0ubmloLmdvdi9nZW8vcXVlcnkvYWNjLmNnaT9hY2M9R1NFMTM2ODMxKSwgaW5jbHVkaW5nIDMxMiw5MjggY2VsbHMgYW5kIDQ1LDk0NyBnZW5lcy4KICAgIAogICAgSGVyZSB3ZSBvbmx5IHVzZSBjZWxscyBmcm9tIG9uZSBJUEYgc3ViamVjdCAqKjIyNUkqKiwgYW5kIHByb3ZpZGUgYSBSIHJkcyBmaWxlIFtJUEZfc2NSTkFfZGF0YS5yZHNdKGh0dHBzOi8vZ2l0aHViLmNvbS9hejdqaDIvU0RlUEVSX0FuYWx5c2lzL2Jsb2IvbWFpbi9SZWFsRGF0YS9JUEYvSVBGX3NjUk5BX2RhdGEucmRzKSBjb250YWluaW5nIGEgKipgU2V1cmF0YCBvYmplY3QqKiB3aXRoIDEyLDA3MCBjZWxscyBhbmQgNjAsNjUxIGdlbmVzLiBUbyBwcm9jZXNzIHRoZSBzY1JOQS1zZXEgZGF0YSBvZiB0aGlzIHN1YmplY3QsIHdlIHVzZSBbU1RBUnNvbG9dKGh0dHBzOi8vZ2l0aHViLmNvbS9hbGV4ZG9iaW4vU1RBUikgdG8gbWFwIHRoZSByZWFkcyB0byByZWZlcmVuY2UgZ2Vub21lIChHUkNoMzgpLCBhbmQgcmVmaW5lZCB0aGUgY2VsbCB0eXBlIGFubm90YXRpb24uIFNvIGJvdGggdGhlIG5VTUkgY291bnQgbWF0cml4IGFuZCBjZWxsIHR5cGUgYW5ub3RhdGlvbiBhcmUgZGlmZmVyZW50IHdpdGggdGhlIHB1Ymxpc2hlZCB2ZXJzaW9uIGluIFtHU0UxMzY4MzFdKGh0dHBzOi8vd3d3Lm5jYmkubmxtLm5paC5nb3YvZ2VvL3F1ZXJ5L2FjYy5jZ2k/YWNjPUdTRTEzNjgzMSkuCiAgICAKICAgIE5vdGUgdGhhdCB3ZSBzZXQgYWxsIGVudHJpZXMgb2YgdGhlIHNwYXJzZSBtYXRyaXggaW4gYGRhdGFgIHNsb3QgYXMgKiowKiogdG8gZGVjcmVhc2UgdGhlIGZpbGUgc2l6ZS4KICAgIAogICAgMi4yIE91dHB1dCBkYXRhIGZpbGVzIGZvciBjZWxsIHR5cGUgZGVjb252b2x1dGlvbgogICAgCiAgICBXZSBzZWxlY3QgKioyNiBtYWpvciBjZWxsIHR5cGVzKiogdG8gd29yayBvbiwgYW5kIGZpbHRlciBvdXQgKipjZWxscyBvZiBvdGhlciBjZWxsIHR5cGVzKiogYW5kICoqZ2VuZXMgTk9UIGV4cHJlc3NlZCBpbiBhbnkgc2VsZWN0ZWQgY2VsbHMqKiwgcmVtYWluaW5nIDExLDIyNyBjZWxscyBhbmQgMzUsNDgzIGdlbmVzLgogICAgCiAgICAqIFJhdyBuVU1JIG9mIDExLDIyNyBjZWxscyB3aXRoIDI2IGNlbGwgdHlwZXMgYW5kIDM1LDQ4MyBnZW5lczogW0lQRl9yZWZfc2NSTkFfY2VsbF9uVU1JLmNzdi5nel0oaHR0cHM6Ly9naXRodWIuY29tL2F6N2poMi9TRGVQRVJfQW5hbHlzaXMvYmxvYi9tYWluL1JlYWxEYXRhL0lQRi9JUEZfcmVmX3NjUk5BX2NlbGxfblVNSS5jc3YuZ3opLgogICAgCiAgICAqIENlbGwgdHlwZSBhbm5vdGF0aW9uIGZvciB0aG9zZSAxMSwyMjcgY2VsbHM6IFtJUEZfcmVmX3NjUk5BX2NlbGxfY2VsbHR5cGUuY3N2XShodHRwczovL2dpdGh1Yi5jb20vYXo3amgyL1NEZVBFUl9BbmFseXNpcy9ibG9iL21haW4vUmVhbERhdGEvSVBGL0lQRl9yZWZfc2NSTkFfY2VsbF9jZWxsdHlwZS5jc3YpLgogICAgCiAgICAqIFdlIGFsc28gbWFudWFsbHkgc2VsZWN0IDIsNTM0IGNlbGwgdHlwZSBzcGVjaWZpYyBtYXJrZXIgZ2VuZXMgZnJvbSB0aGUgZmlsdGVyZWQgZGF0YS4gVG8gdXNlIGl0IGluIFNEZVBFUiBjZWxsIHR5cGUgZGVjb252b2x1dGlvbiwgd2UgY3JlYXRlIGEgMjYgKiAyLDUzNCBtYXRyaXggd2l0aCAqKmFsbCBlbnRyaWVzIGFyZSAwKiosIGFuZCBzZXQgdGhlIHNlbGVjdGVkIDI2IGNlbGwgdHlwZXMgYXMgcm93IG5hbWVzIGFuZCAyLDUzNCBtYXJrZXIgZ2VuZXMgYXMgY29sdW1uIG5hbWVzLiBUaGlzIGNlbGwgdHlwZSBtYWtlciBnZW5lIGV4cHJlc3Npb24gcHJvZmlsZSBpcyBzYXZlZCBpbiBbSVBGX3NlbGVjdGVkXzI1MzRfY2VsbHR5cGVfbWFya2Vycy5jc3ZdKGh0dHBzOi8vZ2l0aHViLmNvbS9hejdqaDIvU0RlUEVSX0FuYWx5c2lzL2Jsb2IvbWFpbi9SZWFsRGF0YS9JUEYvSVBGX3NlbGVjdGVkXzI1MzRfY2VsbHR5cGVfbWFya2Vycy5jc3YpLgoKCgojIFZlcnNpb24KCmBgYHtyfQp2ZXJzaW9uW1sndmVyc2lvbi5zdHJpbmcnXV0KcHJpbnQoc3ByaW50ZignU2V1cmF0IHBhY2thZ2UgdmVyc2lvbjogJXMnLCBwYWNrYWdlVmVyc2lvbignU2V1cmF0JykpKQpgYGAKCgojIFByZXByb2Nlc3MgSVBGIHNwYXRpYWwgZGF0YXNldAoKIyMgUmVhZCBvcmlnaW5hbCBkYXRhIGZpbGUgW0lQRl9zcGF0aWFsX2RhdGEucmRzXShodHRwczovL2dpdGh1Yi5jb20vYXo3amgyL1NEZVBFUl9BbmFseXNpcy9ibG9iL21haW4vUmVhbERhdGEvSVBGL0lQRl9zcGF0aWFsX2RhdGEucmRzKQoKYGBge3J9CmZpbGVfbmFtZSA9IGZpbGUucGF0aChob21lLmRpciwgJ0lQRl9zcGF0aWFsX2RhdGEucmRzJykKb3JnX2RhdGEgPSByZWFkUkRTKGZpbGVfbmFtZSkKcHJpbnQoc3ByaW50ZignbG9hZCBkYXRhIGZyb20gJXMnLCBmaWxlX25hbWUpKQoKcHJpbnQoc3ByaW50Zignc3BvdHM6ICVkOyBnZW5lczogJWQnLCBuY29sKG9yZ19kYXRhKSwgbnJvdyhvcmdfZGF0YSkpKQpgYGAKCgojIyBFeHRyYWN0IHNwb3RzIGNvdmVyZWQgYnkgdGlzc3VlCgpgYGB7cn0KIyBhZGQgdGlzc3VlIGluZGljYXRvciBpbnRvIG1ldGEgZGF0YQp0b3RhbF9zcG90cyA9IG5jb2wob3JnX2RhdGEpCnRtcC5kZiA9IG9yZ19kYXRhQGltYWdlc1tbMV1dQGNvb3JkaW5hdGVzCnRtcC5kZiA9IHRtcC5kZltjb2xuYW1lcyhvcmdfZGF0YSksIF0Kc3RvcGlmbm90KG5yb3codG1wLmRmKSA9PSBuY29sKG9yZ19kYXRhKSkKb3JnX2RhdGFbWyd0aXNzdWUnXV0gPSB0bXAuZGYkdGlzc3VlCgojIHN1YnNldCBzcG90cyBjb3ZlcmVkIGJ5IHRpc3N1ZQpvcmdfZGF0YSA9IHN1YnNldChvcmdfZGF0YSwgc3Vic2V0ID0gdGlzc3VlPT0xKQpwcmludChzcHJpbnRmKCdzcG90cyBjb3ZlcmVkIGJ5IHRpc3N1ZTogJWQgKCUuMmYlJSknLCBuY29sKG9yZ19kYXRhKSwgbmNvbChvcmdfZGF0YSkvdG90YWxfc3BvdHMqMTAwKSkKYGBgCgoKVGlzc3VlIGltYWdlcyBoaWdobGlnaHQgc3BvdHMgY292ZXJlZCBieSB0aXNzdWUKCmBgYHtyfQpJZGVudHMob3JnX2RhdGEpID0gJ3Rpc3N1ZScKU3BhdGlhbERpbVBsb3Qob3JnX2RhdGEsIGNyb3AgPSBGLCBhbHBoYT0wLjIsIGNvbHM9YygnMSc9J2JsdWUnLCAnMCc9J3JlZCcpKSArIE5vTGVnZW5kKCkKYGBgCgoKIyMgRmlsdGVyIG91dCBnZW5lcyBub3QgZGV0ZWN0ZWQgaW4gYW55IHNwb3RzIHdpdGhpbiB0aGlzIHNhbXBsZQoKS2VlcCBnZW5lcyBkZXRlY3RlZCBpbiA+PTEgc3BvdAoKYGBge3J9CnRtcCA9IE1hdHJpeDo6cm93U3VtcyhvcmdfZGF0YUBhc3NheXMkc3BhdGlhbEBjb3VudHMpCm5lZWQuZ2VuZXMgPSBuYW1lcyh0bXApW3RtcD4wXQpwcmludChzcHJpbnRmKCdmaW5hbGx5IGtlZXAgJWQgZ2VuZXMnLCBsZW5ndGgobmVlZC5nZW5lcykpKQogIAojIHN1YnNldCBnZW5lcwpvcmdfZGF0YSA9IHN1YnNldChvcmdfZGF0YSwgZmVhdHVyZXM9bmVlZC5nZW5lcykKYGBgCgpWaW9saW4gcGxvdCBvZiBnZW5lcyBleHByZXNzZWQgaW4gc3BvdHMKCmBgYHtyfQp0bXAgPSBkYXRhLmZyYW1lKHNwb3Rfc3VtPU1hdHJpeDo6Y29sU3VtcyhvcmdfZGF0YUBhc3NheXMkc3BhdGlhbEBjb3VudHM+MCkpCmdncGxvdCh0bXAsIGFlcyh4PScnLCB5PXNwb3Rfc3VtKSkgKwogIGdlb21fdmlvbGluKGZpbGw9J29yYW5nZScsIHdpZHRoPTEpICsKICBnZW9tX2ppdHRlcihzaXplPTAuMSwgcG9zaXRpb249cG9zaXRpb25faml0dGVyKDAuMSkpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzPWMoMCwgOTUwMCksIGJyZWFrcz1zZXEoMCwgOTUwMCwgNTAwKSkgKwogIGxhYnMoeD0nJywgeT0nI2dlbmVzIGV4cHJlc3NlZCBpbiBzcG90JykKYGBgCgoKCiMjIFNhdmUgZmlsZXMgZm9yIGRlY29udm9sdXRpb24KCiMjIyBTcGF0aWFsIHNwb3QgblVNSQoKU2F2ZSBzcG90cyBhbmQgZ2VuZXMgYWZ0ZXIgZmlsdGVyaW5nIGludG8gZmlsZSBbSVBGX3NwYXRpYWxfc3BvdF9uVU1JLmNzdi5nel0oaHR0cHM6Ly9naXRodWIuY29tL2F6N2poMi9TRGVQRVJfQW5hbHlzaXMvYmxvYi9tYWluL1JlYWxEYXRhL0lQRi9JUEZfc3BhdGlhbF9zcG90X25VTUkuY3N2Lmd6KS4gKipSb3dzIGFzIHNwYXRpYWwgc3BvdHMgYW5kIGNvbHVtbnMgYXMgZ2VuZXMqKi4KCmBgYHtyfQpkYXRhLnRhYmxlOjpmd3JpdGUoYXMuZGF0YS5mcmFtZShhcy5tYXRyaXgoTWF0cml4Ojp0KG9yZ19kYXRhQGFzc2F5cyRzcGF0aWFsQGNvdW50cykpKSwgJ0lQRl9zcGF0aWFsX3Nwb3RfblVNSS5jc3YuZ3onLCByb3cubmFtZXMgPSBUKQpwcmludChzcHJpbnRmKCdzYXZlICVkIGdlbmUgblVNSXMgb2YgJWQgc3BhdGlhbCBzcG90cyBpbnRvIGZpbGUgJXMnLCBuY29sKG9yZ19kYXRhKSwgbnJvdyhvcmdfZGF0YSksICdJUEZfc3BhdGlhbF9zcG90X25VTUkuY3N2Lmd6JykpCmBgYAoKCiMjIyBQaHlzaWNhbCBMb2NhdGlvbnMgb2Ygc3BhdGlhbCBzcG90cwoKVGhlIGB4YCBhbmQgYHlgIGNvb3JkaW5hdGVzIG9mIHNwYXRpYWwgc3BvdHMgYXJlIGZyb20gYGNvbGAgYW5kIGByb3dgLCByZXNwZWN0aXZlbHkuIGBjb2xgIGFuZCBgcm93YCBhcmUgZ2VuZXJhdGVkIGJ5IFsxMHggU3BhY2UgUmFuZ2VyXShodHRwczovL3N1cHBvcnQuMTB4Z2Vub21pY3MuY29tL3NwYXRpYWwtZ2VuZS1leHByZXNzaW9uL3NvZnR3YXJlL3BpcGVsaW5lcy9sYXRlc3Qvd2hhdC1pcy1zcGFjZS1yYW5nZXIpLiBTYXZlIGl0IGludG8gZmlsZSBbSVBGX3NwYXRpYWxfc3BvdF9sb2MuY3N2XShodHRwczovL2dpdGh1Yi5jb20vYXo3amgyL1NEZVBFUl9BbmFseXNpcy9ibG9iL21haW4vUmVhbERhdGEvSVBGL0lQRl9zcGF0aWFsX3Nwb3RfbG9jLmNzdikuCgoKYGBge3J9CmxvY2FsX2RmID0gb3JnX2RhdGFAaW1hZ2VzJHgxQGNvb3JkaW5hdGVzICU+JQogIHNlbGVjdChjKCdjb2wnLCAncm93JykpCgpjb2xuYW1lcyhsb2NhbF9kZikgPSBjKCd4JywgJ3knKQoKbG9jYWxfZGZbMTo1LCBdCgp3cml0ZS5jc3YobG9jYWxfZGYsICdJUEZfc3BhdGlhbF9zcG90X2xvYy5jc3YnKQpwcmludChzcHJpbnRmKCdzYXZlIFBoeXNpY2FsIExvY2F0aW9ucyBvZiBzcGF0aWFsIHNwb3RzIGludG8gZmlsZSAlcycsICdJUEZfc3BhdGlhbF9zcG90X2xvYy5jc3YnKSkKYGBgCgoKIyMjIEFkamFjZW5jeSBNYXRyaXggb2Ygc3BhdGlhbCBzcG90cwoKV2UgZGVmaW5lIHRoZSBuZWlnaGJvcmhvb2Qgb2YgYSBzcGF0aWFsIHNwb3QgY29udGFpbnMgdGhlIGFkamFjZW50ICoqbGVmdCoqLCAqKnJpZ2h0KiosICoqdG9wKiogYW5kICoqYm90dG9tKiogc3BvdCwgcGx1cyAqKnRoZSBzZWNvbmQgY2xvc2VzdCBzcG90cyBhdCBsZWZ0IGFuZCByaWdodCoqLCB0aGF0IGlzLCBvbmUgc3BvdCBoYXMgYXQgbW9zdCA2IG5laWdoYm9ycy4KClRoZSBnZW5lcmF0ZWQgQWRqYWNlbmN5IE1hdHJpeCBgQWAgb25seSBjb250YWlucyAqKjEqKiBhbmQgKiowKiosIHdoZXJlIDEgcmVwcmVzZW50cyBjb3JyZXNwb25kaW5nIHR3byBzcG90cyBhcmUgYWRqYWNlbnQgc3BvdHMgYWNjb3JkaW5nIHRvIHRoZSBkZWZpbml0aW9uIG9mIG5laWdoYm9yaG9vZCwgd2hpbGUgdmFsdWUgMCBmb3Igbm9uLWFkamFjZW50IHNwb3RzLiBOb3RlICoqYWxsIGRpYWdvbmFsIGVudHJpZXMgYXJlIDBzKiouCgpBZGphY2VuY3kgTWF0cml4IGFyZSBzYXZlZCBpbnRvIGZpbGUgW0lQRl9zcGF0aWFsX3Nwb3RfYWRqYWNlbmN5X21hdHJpeC5jc3ZdKGh0dHBzOi8vZ2l0aHViLmNvbS9hejdqaDIvU0RlUEVSX0FuYWx5c2lzL2Jsb2IvbWFpbi9SZWFsRGF0YS9JUEYvSVBGX3NwYXRpYWxfc3BvdF9hZGphY2VuY3lfbWF0cml4LmNzdikuCgpgYGB7cn0KZ2V0TmVpZ2hib3VyID0gZnVuY3Rpb24oYXJyYXlfcm93LCBhcnJheV9jb2wpIHsKICAjIGJhc2VkIG9uIHRoZSAocm93LCBjb2wpIG9mIG9uZSBzcG90LCByZXR1cm4gdGhlIChyb3csIGNvbCkgb2YgYWxsIDYgbmVpZ2hib3VycwogIHJldHVybihsaXN0KGMoYXJyYXlfcm93LTEsIGFycmF5X2NvbC0xKSwKICAgICAgICAgICAgICBjKGFycmF5X3Jvdy0xLCBhcnJheV9jb2wrMSksCiAgICAgICAgICAgICAgYyhhcnJheV9yb3csIGFycmF5X2NvbC0yKSwKICAgICAgICAgICAgICBjKGFycmF5X3JvdywgYXJyYXlfY29sKzIpLAogICAgICAgICAgICAgIGMoYXJyYXlfcm93KzEsIGFycmF5X2NvbC0xKSwKICAgICAgICAgICAgICBjKGFycmF5X3JvdysxLCBhcnJheV9jb2wrMSkpKQp9CgojIGFkamFjZW5jeSBtYXRyaXgKQSA9IG1hdHJpeCgwLCBucm93ID0gbnJvdyhsb2NhbF9kZiksIG5jb2wgPSBucm93KGxvY2FsX2RmKSkKcm93Lm5hbWVzKEEpID0gcm93bmFtZXMobG9jYWxfZGYpCmNvbG5hbWVzKEEpID0gcm93bmFtZXMobG9jYWxfZGYpCmZvciAoaSBpbiAxOm5yb3cobG9jYWxfZGYpKSB7CiAgYmFyY29kZSA9IHJvd25hbWVzKGxvY2FsX2RmKVtpXQogIGFycmF5X3JvdyA9IGxvY2FsX2RmW2ksICd5J10KICBhcnJheV9jb2wgPSBsb2NhbF9kZltpLCAneCddCiAgCiAgIyBnZXQgbmVpZ2hib3JzCiAgbmVpZ2hib3VycyA9IGdldE5laWdoYm91cihhcnJheV9yb3csIGFycmF5X2NvbCkKICAKICAjIGZpbGwgdGhlIGFkamFjZW5jeSBtYXRyaXgKICBmb3IgKHRoaXMudmVjIGluIG5laWdoYm91cnMpIHsKICAgIHRtcC5wID0gcm93bmFtZXMobG9jYWxfZGZbbG9jYWxfZGYkeT09dGhpcy52ZWNbMV0gJiBsb2NhbF9kZiR4PT10aGlzLnZlY1syXSwgXSkKICAgIAogICAgaWYgKGxlbmd0aCh0bXAucCkgPj0gMSkgewogICAgICAjIHRhcmdldCBzcG90cyBoYXZlIG5laWdoYm9ycyBpbiBzZWxlY3RlZCBzcG90cwogICAgICBmb3IgKG5laWdoLmJhcmNvZGUgaW4gdG1wLnApIHsKICAgICAgICBBW2JhcmNvZGUsIG5laWdoLmJhcmNvZGVdID0gMQogICAgICB9CiAgICB9CiAgfQp9CgpBWzE6NSwgMTo1XQp3cml0ZS5jc3YoQSwgJ0lQRl9zcGF0aWFsX3Nwb3RfYWRqYWNlbmN5X21hdHJpeC5jc3YnKQpwcmludChzcHJpbnRmKCdzYXZlIEFkamFjZW5jeSBNYXRyaXggb2Ygc3BhdGlhbCBzcG90cyBpbnRvIGZpbGUgJXMnLCAnSVBGX3NwYXRpYWxfc3BvdF9hZGphY2VuY3lfbWF0cml4LmNzdicpKQpgYGAKClBsb3QgQWRqYWNlbmN5IE1hdHJpeC4gRWFjaCBub2RlIGlzIHNwb3QsIHNwb3RzIHdpdGhpbiBuZWlnaGJvcmhvb2QgYXJlIGNvbm5lY3RlZCB3aXRoIGVkZ2VzLgoKTm90ZSAqKnJldmVyc2UgeSBheGlzKiogdG8gdG8gbWFrZSB0aGUgb3JpZ2luICgwLDApIGF0IHRvcCBsZWZ0CgpgYGB7ciwgZmlnLndpZHRoPTI4LCBmaWcuaGVpZ2h0PTI4fQpnID0gZ3JhcGhfZnJvbV9hZGphY2VuY3lfbWF0cml4KEEsICd1bmRpcmVjdGVkJywgYWRkLmNvbG5hbWVzID0gTkEsIGFkZC5yb3duYW1lcyA9IE5BKQojIG1hbnVhbGx5IHNldCBub2RlcyB4IGFuZCB5IGNvb3JkaW5hdGVzCnZlcnRleF9hdHRyKGcsIG5hbWUgPSAneCcpID0gbG9jYWxfZGYkeAp2ZXJ0ZXhfYXR0cihnLCBuYW1lID0gJ3knKSA9IGxvY2FsX2RmJHkKIyByZXZlcnNlIHkgYXhpcyB0byBtYWtlIHRoZSAoMCwwKSBhdCB0b3AgbGVmdApwbG90KGcsIHZlcnRleC5zaXplPTIsIGVkZ2Uud2lkdGg9NCwgbWFyZ2luPS0wLjA1LCB5bGltPWMoMSwgLTEpKQpgYGAKCgojIFByZXByb2Nlc3MgcmVmZXJlbmNlIHNjUk5BLXNlcSBkYXRhCgojIyBSZWFkIG9yaWdpbmFsIGRhdGEgZmlsZSBbSVBGX3NjUk5BX2RhdGEucmRzXShodHRwczovL2dpdGh1Yi5jb20vYXo3amgyL1NEZVBFUl9BbmFseXNpcy9ibG9iL21haW4vUmVhbERhdGEvSVBGL0lQRl9zY1JOQV9kYXRhLnJkcykKCgpgYGB7cn0KZmlsZV9uYW1lID0gZmlsZS5wYXRoKGhvbWUuZGlyLCAnSVBGX3NjUk5BX2RhdGEucmRzJykKcmVmX2RhdGEgPSByZWFkUkRTKGZpbGVfbmFtZSkKcHJpbnQoc3ByaW50ZignbG9hZCBkYXRhIGZyb20gJXMnLCBmaWxlX25hbWUpKQoKY2VsbHR5cGVfY2F0ZWdvcnlfb3JkZXIgPSBjKCJNeWVsb2lkIiwgIkVuZG90aGVsaWFsIiwgIlN0cm9tYWwiLCAiTHltcGhvaWQiLCAiRXBpdGhlbGlhbCIsICJNdWx0aXBsZXQiKSAKCnByaW50KHNwcmludGYoJ2NlbGxzOiAlZDsgZ2VuZXM6ICVkJywgbmNvbChyZWZfZGF0YSksIG5yb3cocmVmX2RhdGEpKSkKcHJpbnQoc3ByaW50ZigndG90YWwgJWQgZGlzdGluY3QgY2VsbCB0eXBlcycsIGxlbmd0aCh1bmlxdWUocmVmX2RhdGEkY2VsbHR5cGUpKSkpCmBgYAoKY2VsbCBjb3VudCBieSBjZWxsIHR5cGVzCgpgYGB7ciwgZmlnLndpZHRoPTE0LCBmaWcuaGVpZ2h0PTEwfQptZXRhLmRhdGEgPSByZWZfZGF0YUBtZXRhLmRhdGEKbWV0YS5kYXRhID0gbWV0YS5kYXRhICU+JSBncm91cF9ieShjZWxsdHlwZV9jYXRlZ29yeSwgY2VsbHR5cGUpICU+JSBzdW1tYXJpc2UoY291bnQ9bigpKQptZXRhLmRhdGEkY2VsbHR5cGVfY2F0ZWdvcnkgPSBmYWN0b3IobWV0YS5kYXRhJGNlbGx0eXBlX2NhdGVnb3J5LCBsZXZlbHMgPSBjZWxsdHlwZV9jYXRlZ29yeV9vcmRlcikKCmdncGxvdChtZXRhLmRhdGEsIGFlcyh4PWNlbGx0eXBlLCB5PWNvdW50LCBsYWJlbD1jb3VudCkpICsKICBnZW9tX2Jhcihwb3NpdGlvbj1wb3NpdGlvbl9kb2RnZTIod2lkdGg9MC45LCBwcmVzZXJ2ZT0ic2luZ2xlIiksIHN0YXQ9ImlkZW50aXR5IikgKwogIGdlb21fdGV4dChwb3NpdGlvbj1wb3NpdGlvbl9kb2RnZTIod2lkdGg9MC45LCBwcmVzZXJ2ZT0ic2luZ2xlIiksIHZqdXN0PTAuNSwgaGp1c3Q9LTAuMSwgYW5nbGU9OTApICsKICB0aGVtZV9idygpICsKICB4bGFiKCIiKSArCiAgeWxhYigiIyBvZiBDZWxscyIpICsKICB0aGVtZShheGlzLnRleHQueD1lbGVtZW50X3RleHQoYW5nbGU9NDUsIHZqdXN0PTEsIGhqdXN0PTEpLAogICAgICAgIGxlZ2VuZC5wb3NpdGlvbj0iYm90dG9tIiwKICAgICAgICBzdHJpcC5wbGFjZW1lbnQgPSAib3V0c2lkZSIsCiAgICAgICAgc3RyaXAuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsPU5BLCBjb2xvdXI9ImdyZXk1MCIpLAogICAgICAgIHBhbmVsLnNwYWNpbmc9dW5pdCgwLCJjbSIpKSArCiAgZmFjZXRfZ3JpZCh+IGNlbGx0eXBlX2NhdGVnb3J5LCBzcGFjZT0iZnJlZV94Iiwgc2NhbGVzPSJmcmVlX3giLCBzd2l0Y2g9IngiKQpgYGAKCiMjIEZpbHRlciBvbiBjZWxscwoKV2Ugb25seSBmb2N1cyBvbiAqKjI2IG1ham9yIGNlbGwgdHlwZXMqKgoKKiAiTXllbG9pZCI6ICJNYWNyb3BoYWdlIiwgIk1hY3JvcGhhZ2VfQWx2ZW9sYXIiLCAiY0RDMSIsICJjREMyIiwgImNNb25vY3l0ZSIsICJuY01vbm9jeXRlIiwgIk1hc3QiCiogIkVuZG90aGVsaWFsIjogIlZFX0FydGVyaWFsIiwgIlZFX1Zlbm91cyIsICJWRV9DYXBpbGxhcnlfQSIsICJWRV9DYXBpbGxhcnlfQiIsICJMeW1waGF0aWMiLCAKKiAiU3Ryb21hbCI6ICJGaWJyb2JsYXN0LUFkdmVudGl0aWFsIiwgIkZpYnJvYmxhc3QtQWlyd2F5IiwgIkZpYnJvYmxhc3QtQWx2ZW9sYXIiLCAiUGVyaWN5dGUtQWx2ZW9sYXIiLCAiU01DLVZhc2N1bGFyIgoqICJMeW1waG9pZCI6ICJUIiwgIkIiLCAiTksiCiogIkVwaXRoZWxpYWwiOiAiQ2lsaWF0ZWQiLCAiQmFzYWwiLCAiR29ibGV0IiwgIkFUSSIsICJBVElJIiwgIkFiZXJyYW50QmFzYWxvaWQiCgpgYGB7cn0KbmVlZF9jZWxsdHlwZXMgPSBjKCJNYWNyb3BoYWdlIiwgIk1hY3JvcGhhZ2VfQWx2ZW9sYXIiLCAiY0RDMSIsICJjREMyIiwgImNNb25vY3l0ZSIsICJuY01vbm9jeXRlIiwgIk1hc3QiLAogICAgICAgICAgICAgICAgICAgIlZFX0FydGVyaWFsIiwgIlZFX1Zlbm91cyIsICJWRV9DYXBpbGxhcnlfQSIsICJWRV9DYXBpbGxhcnlfQiIsICJMeW1waGF0aWMiLAogICAgICAgICAgICAgICAgICAgIkZpYnJvYmxhc3QtQWR2ZW50aXRpYWwiLCAiRmlicm9ibGFzdC1BaXJ3YXkiLCAiRmlicm9ibGFzdC1BbHZlb2xhciIsICJQZXJpY3l0ZS1BbHZlb2xhciIsICJTTUMtVmFzY3VsYXIiLAogICAgICAgICAgICAgICAgICAgIlQiLCAiQiIsICJOSyIsCiAgICAgICAgICAgICAgICAgICAiQ2lsaWF0ZWQiLCAiQmFzYWwiLCAiR29ibGV0IiwgIkFUSSIsICJBVElJIiwgIkFiZXJyYW50QmFzYWxvaWQiKQpyZWZfZGF0YSA9IHN1YnNldChyZWZfZGF0YSwgc3Vic2V0ID0gY2VsbHR5cGUgJWluJSBuZWVkX2NlbGx0eXBlcykKcHJpbnQoc3ByaW50ZigncmVtYWluIGNlbGxzOiAlZDsgZ2VuZXM6ICVkJywgbmNvbChyZWZfZGF0YSksIG5yb3cocmVmX2RhdGEpKSkKYGBgCgoKY2VsbCBjb3VudCBvZiB0aG9zZSAyNiBtYWpvciBjZWxsIHR5cGVzCgpgYGB7ciwgZmlnLndpZHRoPTksIGZpZy5oZWlnaHQ9N30KbWV0YS5kYXRhID0gcmVmX2RhdGFAbWV0YS5kYXRhCm1ldGEuZGF0YSA9IG1ldGEuZGF0YSAlPiUgZ3JvdXBfYnkoY2VsbHR5cGVfY2F0ZWdvcnksIGNlbGx0eXBlKSAlPiUgc3VtbWFyaXNlKGNvdW50PW4oKSkKbWV0YS5kYXRhJGNlbGx0eXBlX2NhdGVnb3J5ID0gZmFjdG9yKG1ldGEuZGF0YSRjZWxsdHlwZV9jYXRlZ29yeSwgbGV2ZWxzID0gY2VsbHR5cGVfY2F0ZWdvcnlfb3JkZXIpCm1ldGEuZGF0YSRjZWxsdHlwZSA9IGZhY3RvcihtZXRhLmRhdGEkY2VsbHR5cGUsIGxldmVscyA9IG5lZWRfY2VsbHR5cGVzKQoKZ2dwbG90KG1ldGEuZGF0YSwgYWVzKHg9Y2VsbHR5cGUsIHk9Y291bnQsIGxhYmVsPWNvdW50KSkgKwogIGdlb21fYmFyKHBvc2l0aW9uPXBvc2l0aW9uX2RvZGdlMih3aWR0aD0wLjksIHByZXNlcnZlPSJzaW5nbGUiKSwgc3RhdD0iaWRlbnRpdHkiKSArCiAgZ2VvbV90ZXh0KHBvc2l0aW9uPXBvc2l0aW9uX2RvZGdlMih3aWR0aD0wLjksIHByZXNlcnZlPSJzaW5nbGUiKSwgdmp1c3Q9MC41LCBoanVzdD0tMC4xLCBhbmdsZT05MCkgKwogIHRoZW1lX2J3KCkgKwogIHhsYWIoIiIpICsKICB5bGFiKCIjIG9mIENlbGxzIikgKwogIHRoZW1lKGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChhbmdsZT00NSwgdmp1c3Q9MSwgaGp1c3Q9MSksCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uPSJib3R0b20iLAogICAgICAgIHN0cmlwLnBsYWNlbWVudCA9ICJvdXRzaWRlIiwKICAgICAgICBzdHJpcC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGw9TkEsIGNvbG91cj0iZ3JleTUwIiksCiAgICAgICAgcGFuZWwuc3BhY2luZz11bml0KDAsImNtIikpICsKICBmYWNldF9ncmlkKH4gY2VsbHR5cGVfY2F0ZWdvcnksIHNwYWNlPSJmcmVlX3giLCBzY2FsZXM9ImZyZWVfeCIsIHN3aXRjaD0ieCIpICsKICB5bGltKDAsIDU0MDApCmBgYAoKCiMjIEZpbHRlciBvbiBnZW5lcwoKV2UgZXhjbHVkZXMgZ2VuZXMgTk9UIGV4cHJlc3NlZCBpbiBhbnkgY2VsbHMgd2l0aGluIHRoZSAyNiBtYWpvciBjZWxsIHR5cGVzCgpgYGB7cn0KZ2VuZV9zdW0gPSBNYXRyaXg6OnJvd1N1bXMocmVmX2RhdGFAYXNzYXlzJFJOQUBjb3VudHMpCmtlZXBfZ2VuZXMgPSBuYW1lcyhnZW5lX3N1bSlbZ2VuZV9zdW0+MF0KcmVmX2RhdGEgPSBzdWJzZXQocmVmX2RhdGEsIGZlYXR1cmVzID0ga2VlcF9nZW5lcykKcHJpbnQoc3ByaW50ZigncmVtYWluIGNlbGxzOiAlZDsgZ2VuZXM6ICVkJywgbmNvbChyZWZfZGF0YSksIG5yb3cocmVmX2RhdGEpKSkKYGBgCgojIyBTYXZlIGZpbGVzCgpTYXZlIGNlbGwgdHlwZSBhbm5vdGF0aW9uIG9mIHNlbGVjdGVkIGNlbGxzIHRvIGZpbGUgW0lQRl9yZWZfc2NSTkFfY2VsbF9jZWxsdHlwZS5jc3ZdKGh0dHBzOi8vZ2l0aHViLmNvbS9hejdqaDIvU0RlUEVSX0FuYWx5c2lzL2Jsb2IvbWFpbi9SZWFsRGF0YS9JUEYvSVBGX3JlZl9zY1JOQV9jZWxsX2NlbGx0eXBlLmNzdikuCgpgYGB7cn0Kd3JpdGUuY3N2KHJlZl9kYXRhQG1ldGEuZGF0YVssICdjZWxsdHlwZScsIGRyb3A9Rl0sICdJUEZfcmVmX3NjUk5BX2NlbGxfY2VsbHR5cGUuY3N2JykKcHJpbnQoc3ByaW50Zignc2F2ZSBjZWxsIHR5cGUgYW5ub3RhdGlvbiBvZiByZWZlcmVuY2Ugc2NSTkEtc2VxIGNlbGxzIGludG8gZmlsZSAlcycsICdJUEZfcmVmX3NjUk5BX2NlbGxfY2VsbHR5cGUuY3N2JykpCmBgYAoKClNhdmUgc2NSTkEtc2VxIG5VTUkgbWF0cml4IHRvIGZpbGUgW0lQRl9yZWZfc2NSTkFfY2VsbF9uVU1JLmNzdi5nel0oaHR0cHM6Ly9naXRodWIuY29tL2F6N2poMi9TRGVQRVJfQW5hbHlzaXMvYmxvYi9tYWluL1JlYWxEYXRhL0lQRi9JUEZfcmVmX3NjUk5BX2NlbGxfblVNSS5jc3YuZ3opCgpgYGB7cn0KcmVmX2RmID0gYXMuZGF0YS5mcmFtZShhcy5tYXRyaXgoTWF0cml4Ojp0KHJlZl9kYXRhQGFzc2F5cyRSTkFAY291bnRzKSksIGNoZWNrLm5hbWVzPUYpCnJlZl9kZlsxOjUsIDE6NV0KCmRhdGEudGFibGU6OmZ3cml0ZShyZWZfZGYsICdJUEZfcmVmX3NjUk5BX2NlbGxfblVNSS5jc3YuZ3onLCByb3cubmFtZXMgPSBUKQpwcmludChzcHJpbnRmKCdzYXZlIG5VTUkgbWF0cml4IG9mIHJlZmVyZW5jZSBzY1JOQS1zZXEgY2VsbHMgaW50byBnemlwIGNvbXByZXNzZWQgZmlsZSAlcycsICdJUEZfcmVmX3NjUk5BX2NlbGxfblVNSS5jc3YuZ3onKSkKYGBgCgoK